package com.demo.camera2;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Range;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class EasyCamera2 {

    private static final String TAG = "EasyCamera2";

    private Context context;

    private HandlerThread handlerThread;

    private Handler backgroundHandler;

    private CameraDevice cameraDevice;

    private CameraCaptureSession cameraCaptureSession;

    private CaptureRequest.Builder captureRequest;

    private ImageReader imageReader;

    private PreviewCallback previewCallback;

    private byte[] y, uv, yuv;

    public EasyCamera2(Context context) {
        this.context = context;
    }

    public void setPreviewCallback(PreviewCallback previewCallback) {
        this.previewCallback = previewCallback;
    }

    public int open(String productName, int width, int height) {
        int cameraIndex = -1;

        File[] videoFiles = new File("/sys/class/video4linux")
                .listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().startsWith("video");
            }
        });
        assert videoFiles != null;
        for (File videoFile : videoFiles) {
            InputStreamReader inputStreamReader = null;
            BufferedReader bufferedReader = null;
            try {
                inputStreamReader = new InputStreamReader(new FileInputStream(new File(videoFile, "name")));
                bufferedReader = new BufferedReader(inputStreamReader);
                String name = bufferedReader.readLine();
                if (productName.equals(name)) {
                    String index = videoFile.getName().substring(5);
                    cameraIndex = Integer.parseInt(index);
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (bufferedReader != null) {
                        bufferedReader.close();
                    }
                    if (inputStreamReader != null) {
                        inputStreamReader.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        if (cameraIndex != -1) {
            open(cameraIndex, width, height);
        }
        return cameraIndex;
    }

    @SuppressLint("MissingPermission")
    public void open(int cameraIndex, int width, int height) {
        handlerThread = new HandlerThread("CameraThread");
        handlerThread.start();
        backgroundHandler = new Handler(handlerThread.getLooper());

        CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraIdList = cameraManager.getCameraIdList();
            for (String s : cameraIdList) {
                Log.d(TAG, "camera id: " + s);
            }

            String cameraId = cameraIdList[cameraIndex];
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);

            cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(CameraDevice camera) {
                    Log.d(TAG, "onOpened: ");

                    cameraDevice = camera;

                    try {
                        imageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
                        imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
                            @Override
                            public void onImageAvailable(ImageReader reader) {
                                Image image = reader.acquireLatestImage();
                                if (image != null) {
                                    int width = image.getWidth();
                                    int height = image.getHeight();

                                    if (yuv == null) {
                                        y = new byte[width * height];
                                        uv = new byte[width * height / 2];
                                        yuv = new byte[width * height * 12 / 8];
                                    }

                                    Image.Plane[] planes = image.getPlanes();

                                    ByteBuffer yBuffer = planes[0].getBuffer();
                                    yBuffer.get(y, 0, yBuffer.limit());

                                    ByteBuffer uvBuffer = planes[2].getBuffer();
                                    uvBuffer.get(uv, 0, uvBuffer.limit());

                                    System.arraycopy(y, 0, yuv, 0, y.length);
                                    System.arraycopy(uv, 0, yuv, y.length, uv.length);

                                    if (previewCallback != null) {
                                        previewCallback.onPreviewFrame(yuv, width, height);
                                    }
                                    image.close();
                                }
                            }
                        }, backgroundHandler);

                        Range<Integer>[] ranges = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
                        Range<Integer> maxFps = getMaxFps(ranges);

                        captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                        captureRequest.addTarget(imageReader.getSurface());
                        if (maxFps != null) {
                            captureRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, maxFps);
                        }

                        cameraDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()),
                                new CameraCaptureSession.StateCallback() {
                                    @Override
                                    public void onConfigured(CameraCaptureSession session) {
                                        Log.d(TAG, "onConfigured: ");
                                        cameraCaptureSession = session;
                                        try {
                                            cameraCaptureSession.setRepeatingRequest(captureRequest.build(), null, backgroundHandler);
                                        } catch (CameraAccessException e) {
                                            e.printStackTrace();
                                        }
                                    }

                                    @Override
                                    public void onConfigureFailed(CameraCaptureSession session) {
                                        Log.d(TAG, "onConfigureFailed: ");
                                    }
                                }, backgroundHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onDisconnected(CameraDevice camera) {
                    Log.d(TAG, "onDisconnected: ");
                }

                @Override
                public void onError(CameraDevice camera, int error) {
                    Log.d(TAG, "onError: ");
                }
            }, backgroundHandler);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void close() {
        if (cameraDevice != null) {
            cameraDevice.close();
            cameraDevice = null;
        }
        if (cameraCaptureSession != null) {
            cameraCaptureSession.close();
            cameraCaptureSession = null;
        }
        if (imageReader != null) {
            imageReader.close();
        }

        if (handlerThread != null) {
            handlerThread.quitSafely();
        }
    }

    private Range<Integer> getMaxFps(Range<Integer>[] ranges) {
        if (ranges != null && ranges.length > 0) {
            Range<Integer> range = ranges[0];

            for (int i = 1; i < ranges.length; i++) {
                Range<Integer> temp = ranges[i];
                if (temp.getLower() * temp.getUpper()
                        > range.getLower() * range.getUpper()) {
                    range = temp;
                }
            }

            return range;
        }
        return null;
    }

}
